import { Item } from "./item"
import { Equip } from "@/core/equips"
/**
 * @Author: Kritsu
 * @Date:   2021/11/09 16:01:05
 * @Last Modified by:   Kritsu
 * @Last Modified time: 2021/11/17 18:36:42
 */
import { BufferEntry, BufferEntryOptions, createBufferEntry, mergeBufferEntry } from "@/core/entries/buffer"
import { createDelearEntry, DealerEntry, DealerEntryOptions, mergeDealerEntry } from "@/core/entries/dealer"
import { createSkillEntry, SkillEntry, SkillEntryOptions, SkillResult } from "@/core/skill"
import { createCharacterStatus, mergeStatusOptions, Status, StatusOptions } from "./status"
import { isBuffer } from "@/store"

interface ModifierOption {
    value: number
    level: number
}

export interface CalcDataOptions {
    status?: Status

    dungeon_status?: Status

    skill_entries?: SkillEntry[]

    buff_entry?: BufferEntry

    deal_entry?: DealerEntry
}

export type CalcListener = (self: CalcData, ...args: any) => void

export class CalcData {
    name?: string

    fame: number

    readonly buff_entry: BufferEntry

    readonly deal_entry: DealerEntry

    readonly skill_entries: SkillEntry[] = []

    readonly dungeon_status: Status

    readonly status: Status

    is_buffer: boolean

    next?: CalcData

    is_record: boolean

    custom_comments: string[] = []

    modifiers: [string, string, number][] = []

    disable_buffer: boolean = false

    disable_dealer: boolean = false

    //监听器
    listeners: Map<string, Set<CalcListener>>

    //等待区
    waits: Map<string, Array<any>>

    fame_map: Map<string, number>

    constructor({ status, buff_entry, deal_entry, skill_entries, dungeon_status }: CalcDataOptions = {}) {
        this.deal_entry = deal_entry ?? createDelearEntry()
        this.buff_entry = buff_entry ?? createBufferEntry()
        this.status = status ?? createCharacterStatus()
        this.dungeon_status = dungeon_status ?? createCharacterStatus()
        this.is_buffer = isBuffer()
        this.is_record = false
        this.listeners = new Map()
        this.waits = new Map()
        this.fame_map = new Map()
        this.fame = 0
    }

    on(name: string, fn: CalcListener) {
        let list = this.listeners.get(name) ?? new Set()
        list.add(fn)
        this.listeners.set(name, list)

        let wait = this.waits.get(name) ?? []
        for (let args of wait) {
            this.emit(name, ...args)
        }
    }

    remove(name: string, fn: CalcListener) {
        const list = this.listeners.get(name)
        if (list) {
            list.delete(fn)
        }
    }

    // 只执行一次 且将会把
    once(name: string, fn: CalcListener, matchs: any[] = []) {
        const keys = name.split(":")
        name = keys[0]

        let matchCount = 0
        const wrapper: CalcListener = (self, ...args) => {
            if (matchs.includes(args[0])) {
                ++matchCount
            }

            if (matchCount == matchs.length) {
                fn(self, args)

                this.remove(name, wrapper)
            }
        }

        this.on(name, wrapper)
    }

    emit(name: string, ...args: any[]) {
        const list = this.listeners.get(name)
        if (list) {
            for (let fn of list) {
                fn(this, ...args)
            }
        }
    }

    /**
     ** 等待
     */
    listen(name: string, ...args: any[]) {
        this.emit(name, ...args)
        const array = this.waits.get(name) ?? []
        array.push(args)
        this.waits.set(name, array)
    }

    force(item?: Item) {
        if (item) {
            item.effect?.(this)
            item.dungeon_effect?.(this)
            this.changeFame(item.fame ?? 0)
        }
    }

    changeFame(fame: number, name?: string) {
        if (fame) {
            let value = fame
            if (name) {
                const old_fame = this.fame_map.get(name) ?? 0
                value = fame - old_fame
                this.fame_map.set(name, Math.max(old_fame, fame))
            }
            if (value > 0) {
                this.fame += value
            }
        }
    }

    changeStatus(options?: StatusOptions) {
        if (options) {
            this.proxy($ => mergeStatusOptions($.status, options))
        }
    }

    changeDungeonStatus(options?: StatusOptions) {
        if (options) {
            this.proxy($ => mergeStatusOptions($.dungeon_status, options))
        }
    }

    changeDamage(options: DealerEntryOptions) {
        this.proxy($ => mergeDealerEntry($.deal_entry, options))
    }

    changeBuff(options: BufferEntryOptions) {
        if (this.is_buffer) {
            this.proxy($ => mergeBufferEntry($.buff_entry, options))
        }
    }

    changeSkill(...options: SkillEntryOptions[]) {
        this.proxy($ => $.skill_entries.push(...options.map(createSkillEntry)))
    }

    customComment(comment: string) {
        this.proxy($ => $.custom_comments.push(comment))
    }

    apply(data: CalcData) {
        this.changeBuff(data.buff_entry)
        this.changeDamage(data.deal_entry)
        this.changeStatus(data.status)
        this.changeSkill(...data.skill_entries)
        this.changeFame(data.fame)
        this.proxy($ => $.modifiers.push(...data.modifiers))
    }

    //开启记录模式
    record() {
        this.is_record = !this.is_record
        if (this.is_record) {
            this.next = undefined
        }
    }

    /**
     * 代理 如果开启记录模式,则将数据同步到next
     * @param callback
     */
    proxy(callback: ($: CalcData) => void) {
        let _this: CalcData = this

        if (this.is_record) {
            while (_this?.next) {
                _this = _this.next
            }
            _this.next = new CalcData()
            callback(_this.next)
        }
        callback(this)
    }

    toString() {
        return this.buff_entry + ""
    }

    /**
     * 历史记录
     * 如果未记录则返回自身
     */
    *history() {
        let _this: CalcData | undefined = this.next ?? this
        do {
            yield _this
        } while ((_this = _this.next))
    }
}

export interface CalcContext {
    [key: string]: any
}

export interface CalcResult {
    context: CalcData

    final_results: Status

    rate: number

    skill_datas: SkillResult[]
}

//计算单元
export abstract class CalcUnit {
    constructor() {}

    abstract collect(chracter: CalcContext): CalcData[]
}

export type CalcFunction = (...args: any[]) => CalcData[]
